package com.stone.serverless.core.classloader;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONNull;
import com.stone.serverless.sdk.loader.ModuleClassLoader;
import lombok.SneakyThrows;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @Description: TODO
 * @Author: MECHREVO
 * @Date: 2022/1/13 9:58
 * @Version: 1.0.0
 */
public abstract class JarModuleClassLoader extends URLClassLoader implements ModuleClassLoader {

    //属于本类加载器加载的jar包
    private List<JarFile> jarFiles = new ArrayList<>();

    //保存本类加载器加载的class字节码
    protected Map<String, byte[]> classBytesMap = new HashMap<>();


    public JarModuleClassLoader(ClassLoader parentClassLoader) {
        super(new URL[]{}, parentClassLoader == null ? ClassLoaderUtil.getClassLoader() : parentClassLoader);
    }

    /**
     * 初始化类加载器
     */
    @SneakyThrows
    protected void initLoader(File file) {
        //System.out.println("start addURL");
        JarFile jarFile = new JarFile(file);
        this.addURL(URLUtil.getURL(file));
        //System.out.println("start unzip jar");
        this.unzipJar(jarFile);
    }


    /**
     * Finds and loads the class with the specified name from the URL search
     * path. Any URLs referring to JAR files are loaded and opened as needed
     * until the class is found.
     *
     * @param name the name of the class
     * @return the resulting class
     * @throws ClassNotFoundException if the class could not be found,
     *                                or if the loader is closed.
     * @throws NullPointerException   if {@code name} is {@code null}.
     */
    @Override
    protected Class<?> findClass(final String name)
            throws ClassNotFoundException {
        Class<?> aClass = null;
        try {
            aClass = super.findClass(name);
        } catch (Throwable e) {
            byte[] bytes = this.classBytesMap.get(name);
            if (ObjectUtil.isNotNull(bytes) && bytes.length != 0) {
                //调用ClassLoader的defineClass方法将二进制数据转换成class对象
                try {
                    aClass = defineClass(name, bytes, 0, bytes.length);
                } catch (Throwable ee) {
                }
            }

        }
        return aClass;
    }

    //重写loadClass方法
    //改写loadClass方式
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> loadClass = findClass(name);
        if (ObjectUtil.isNotNull(loadClass)) {
            return loadClass;
        }
        try {
            loadClass = super.loadClass(name);
        } catch (Throwable e) {
            // System.out.println("module name [" + this.componentBizConfig.getComponentName() + "] load Class " + name + " ex " + e.getMessage());
        }
        return loadClass;
    }

    @Override
    public ClassLoader getClassloader() {
        return this;
    }

    /**
     * 方法描述 初始化类加载器，保存字节码
     *
     * @param jarFile
     * @method init
     */
    @SneakyThrows
    protected void unzipJar(JarFile jarFile) {
        this.jarFiles.add(jarFile);
        //解析jar包每一项
        Enumeration<JarEntry> en = jarFile.entries();
        while (en.hasMoreElements()) {
            InputStream input = null;
            String name = null;
            try {
                JarEntry je = en.nextElement();
                name = je.getName();
                input = jarFile.getInputStream(je);
                //这里添加了路径扫描限制
                this.loadClassByte(name, input);
                //如果有子jar
                this.loadChildrenJar(name);
            } catch (IOException e) {
                System.out.println("加载文件" + name + "失败," + e.getMessage());
            } finally {
                IoUtil.close(input);
            }
        }
    }


    public abstract void loadBizComponent(String aClass);


    public void loadClassByte(String name, InputStream inputStream) {
        if (!name.endsWith(".class")) {
            return;
        }
        //截取获得class名称
        String className = name.replace(".class", "").replaceAll("/", ".");
        byte[] classBytes = IoUtil.readBytes(inputStream);
        classBytesMap.put(className, classBytes);
    }


    @SneakyThrows
    public void loadChildrenJar(String name) {
        if (!name.endsWith(".jar")) {
            return;
        }

        File jarFile = this.getUnzipJarFile(name);

        if (ObjectUtil.isNotNull(jarFile)) {
            this.addURL(URLUtil.getURL(jarFile));
            try {
                JarFile sjarFile = new JarFile(jarFile);
                this.unzipJar(sjarFile);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public abstract File getUnzipJarFile(String jarFileName);


    public Map jsonNullProcessor(Map map) {
        Map res = new HashMap();
        if (map == null) {
            return res;
        }
        map.forEach((k, v) -> {
            if (v instanceof Map) {
                res.put(k, jsonNullProcessor((Map) v));
            } else if (v instanceof JSONNull) {
                res.put(k, null);
            } else {
                res.put(k, v);
            }
        });
        return res;
    }


    /**
     * 卸载
     */
    @Override
    public void uninstall() {
        this.classBytesMap = null;
        this.close();
        this.jarFiles.forEach(v -> {
            try {
                v.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        this.jarFiles.clear();
        this.jarFiles = null;
    }

    @Override
    public URL[] getUrls() {
        return this.getURLs();
    }

    @Override
    public void close() {
        try {
            super.close();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }

    }
}
